/**
* @file Lift.java
*
* @brief Diese Datei enthält die Lift-Klasse.
*
* @author Alrik Künne
*
* @date 01.06.2012 - erste Version
* @date 19.06.2012 - einige Veränderungen, Multi-Threading
**/


/** 
* @class Lift 
* 
* @brief die Klasse der Liftsteuerung
* 
* Die Liftklasse ist der Kern des Programms, sie verwaltet alle wichtigen Infos 
* und steuert den Gesamtablauf der Liftsteuerung
*/ 
public class Lift{
	
	public static final int etagen=9;
	public static final int overloadborder=13;
	
	private boolean hoch[];
	private boolean runter[];
	private int peoplerequest[];
	
	private int door;
	private int pos;
	private int Etage;
	private int time;
	private int Carry;
	private boolean timeinterruptflag;
	private boolean bewegung;
	private boolean driveup;
	private int showOverloadwithpeople;
	private boolean displayoverload;
	
	public DoorCloseAction doorcloseAction;
	public DoorOpenAction dooropenAction;
	public OverloadAction overloadAction;
	public InterruptAction interruptAction;
	public CabineDriveAction cabinedriveAction;
	
	/**
	 * @brief Der Konstruktor
	 *
	 * initialisiert den Lift mit allen Einstellungen und öffnet die Türen, wenn die Methode fertig ist
	 */
	public Lift(){
		hoch=new boolean[etagen];
		runter=new boolean[etagen];
		peoplerequest=new int[etagen];
		Etage=1;
		pos=0;
		bewegung=false;
		door=0;
		Carry=0;
		driveup=true;
		time=-1;
		timeinterruptflag=false;
		displayoverload=false;
		this.openDoors();
	}
		
	/**
	 * @brief Infos zu den Rufen Hoch
	 *
	 * @return das Feld hoch
	 * 
	 * gibt das Feld mit den Rufen nach oben
	 */
	public synchronized boolean[] returnhoch(){
		return hoch;
	}
	
	/**
	 * @brief Infos zu den Rufen Hoch
	 *
	 * @return das Feld runter
	 * 
	 * gibt das Feld mit den Rufen nach unten
	 */
	public synchronized boolean[] returnrunter(){
		return runter;
	}
	
	/**
	 * @brief Infos zu den Fahrtwünschen
	 *
	 * @return das Feld peoplerequest;
	 * 
	 * gibt das Feld mit den Fahrtwünschen in alle Richtungen
	 */
	public synchronized int[] returnpeoplerequest(){
		return peoplerequest;
	}
	
	/**
	 * @brief stellt fest ob die Fahrkabine noch (weiter) nach oben fahren muss.
	 *
	 * @return boolean 
	 *  
	 * es geht so wohl ganz oben bzw. ganz unten. Es ist unabhängig ob es sich um einen Fahrtwunsch oder Ruf handelt.
	 */
	public synchronized boolean isARequestoverme(){
		int tmpEtage=Etage+1;
		while(tmpEtage<10){
			if((hoch[tmpEtage-1]==true) || (runter[tmpEtage-1]==true) || (peoplerequest[tmpEtage-1]!=0)){
				return true;
			}
			tmpEtage++;
		}
		return false;
	}
	
	/**
	 * @brief stellt fest ob die Fahrkabine noch (weiter) nach unten fahren muss.
	 *
	 * @return boolean 
	 *  
	 * es geht so wohl ganz oben bzw. ganz unten. Es ist unabhängig ob es sich um einen Fahrtwunsch oder Ruf handelt.
	 */
	public synchronized boolean isARequestunderme(){
		int tmpEtage=Etage-1;
		while(tmpEtage>0){
			if((hoch[tmpEtage-1]==true) || (runter[tmpEtage-1]==true) || (peoplerequest[tmpEtage-1]!=0)){
				return true;
			}
			tmpEtage--;
		}
		return false;
	}
	
	/**
	 * @brief stellt fest ob die Fahrkabine noch (weiter) nach oben fahren muss.
	 *
	 * @return boolean über den Erfolg (true) oder Misserfolg (false)
	 *  
	 * wenn eine Ruftaste gedrückt wurde, wird hier festgestellt, ob der Ruf gülig ist<br/>
	 * ungültig wäre er, wenn die Kabine sich bereits offen in der Etage befindet. 
	 */
	public synchronized boolean setFahrtwunsch(int Fahrtwunsch){
		if ((!ismov()) && (getetage()==Math.abs(Fahrtwunsch)))
			return false;
		if(Fahrtwunsch<0)
			runter[-Fahrtwunsch-1]=true;
		else
			hoch[Fahrtwunsch-1]=true;
		
		System.out.println("Fahrtwunsch:"+ Fahrtwunsch);
		if(cabinedriveAction==null && overloadAction==null){
			cabinedriveAction=new CabineDriveAction();
			cabinedriveAction.start();
		}	
		
		return true;

	};
	
	/**
	 * @brief gibt an, ob die Kabine sich bewegt
	 *
	 * @return boolean true, wenn sie sich bewegt, false wenn nicht
	 */
	public synchronized boolean ismov(){
		return bewegung;
	}
	
	/**
	 * @brief gibt die aktuelle Etage zurück, in der sich die Kabine befindet.
	 *
	 * @return int Etage, unabhängig ob sich die Kabine bewegt oder nicht.
	 */
	public synchronized int getetage(){
		return Etage;
	}
	
	/**
	 * @brief gibt den Zustand der Türen zurück
	 *
	 * @return int Türzustand, gibt den aktuellen Zustand der Türen zurück (0=zu), (40=offen)
	 */
	public synchronized int getdoorcond(){
		return door;
	}
	
	/**
	 * @brief gibt den Versatz der Kabine zurück
	 *
	 * @return int Versatz, gibt den aktuellen Versatz der Kabine zurück (0=keinen), (40=max)
	 */
	public synchronized int getBewegung(){
		return pos;
	}
	
	/**
	 * @brief leitet das öffnen der Türen ein
	 *
	 * Es wird geprüft, ob die Türen geöffnet werden dürfen. Falls ja wird ein Objekt vom Typ DooropenAction erzeugt.
	 */
	public void openDoors(){
		if((doorcloseAction!=null) && (doorcloseAction.isAlive()) ){
			doorcloseAction.interrupt();
		}
		if((dooropenAction==null) && (!ismov())){ //damit die Tür nicht wärend der Fahrt aufgeht
			dooropenAction=new DoorOpenAction();
			dooropenAction.start();
		}
		
	}
	
	/**
	 * @brief leitet das schließen der Türen ein
	 *
	 * Es wird geprüft, ob die Türen geschlossen werden dürfen. Falls ja wird ein Objekt vom Typ DoorcloseAction erzeugt.
	 */
	public void closeDoors(){
		if((doorcloseAction==null) && (dooropenAction==null)){
			if(interruptAction!=null){
				interruptAction.interrupt();
				//das beschleunigte Abfahren wird hier eingeleitet
			}
			doorcloseAction=new DoorCloseAction();
			doorcloseAction.start();
		}
	}
	
	/**
	 * @brief verbucht die Personen die zusteigen
	 * 
	 * @param Etage - die Etage, in die die Leute verbucht werden sollen
	 * 
	 * @param newpeople - Anzahl der Personen die in die Etage verbucht werden sollen.
	 * 
	 * @return Carry, der Übersatz, der von dieser Buchung nicht verbucht wurde und somit wieder aussteigen muss. Wenn 0, dann passen alle rein.
	 *
	 * Es wird ein InterruptAction-Objekt erzeugt, falls noch keins existiert.
	 * 
	 */
	public synchronized int addPeopleandreturnCarry(int Etage, int newpeople){
		Carry=getAllPeople()+newpeople-overloadborder>0?getAllPeople()+newpeople-overloadborder:0;
		newpeople-=Carry;
		peoplerequest[Etage-1]+=newpeople;
		if(interruptAction==null){
			interruptAction=new InterruptAction();
			interruptAction.start();
		}	
		return Carry;
	}
	
	/**
	 * @brief gibt die Anzahl aller Personen, die sich im Fahrstuhl befinden zurück
	 *
	 * @return Summe der Personen
	 */
	public synchronized int getAllPeople(){
		int sum=0;
		for(int i=0; i<9; i++){
			sum+=peoplerequest[i];
		}
		return sum+Carry;
	}
	
	/**
	 * @brief gibt die (letzte) Fahrtrichtung zurück
	 *
	 * @return boolean driveup (true=hoch, false=runter)
	 */
	public synchronized boolean getdriveup(){
		return driveup;
	}
	
	/**
	 * @brief gibt die Zeit in sec zurück, die der Fahrstuhl noch wartet, bis er die Türen das nächste mal schließt
	 *
	 * @return int Wartezeit in Sekunden
	 * 
	 * es ist ein voraussichtlicher Wert
	 */
	public synchronized int getInterruptTime(){
		return time;
	}
	
	/**
	 * @brief setzt die Wartezeit
	 *
	 * @param sec - die Wartezeit in sec
	 * 
	 * diese Klasse zählt die Zeit nicht runter, sie dient als Werteschnittstelle
	 */
	public synchronized void setInterruptTime(int sec){
		time=sec;
	}
	
	/**
	 * @brief gibt das timeinterrupt-Flag zurück
	 *
	 * @return boolean flag (true=interrupted, false=not interrupted)
	 */
	public synchronized boolean gettimeinterruptflag(){
		return timeinterruptflag;
	}
	
	/**
	 * @brief setzt das timeinterrupt-Flag zurück
	 *
	 * @param flag - (true=interrupted, false=not interrupted)
	 * 
	 * diese Klasse führt keine Timeinterruption durch, sie dient als Werteschnittstelle<br/>
	 * Timeinterruption dient zum Anhalten der Zeit, wenn z.b. Dialoge geöffnet sind oder eine Überlast auftritt.
	 */
	public synchronized void settimeinterruptflag(boolean flag){
		timeinterruptflag=flag;
	}
	
	/**
	 * @brief setzt die Anzahl der Personen, die zu viel sind
	 *
	 * @param people - Anahlt der Personen, die zu viel sind
	 * 
	 * diese Klasse führt keine Timeinterruption durch, sie dient als Werteschnittstelle<br/>
	 * Timeinterruption dient zum Anhalten der Zeit, wenn z.b. Dialoge geöffnet sind oder eine Überlast auftritt.
	 */
	private synchronized void setshowOverloadwith(int people){
		showOverloadwithpeople=people;
	}
	
	/**
	 * @brief holt die Anzahl der Personen, die zu viel sind
	 *
	 * @return Anzahl der Personen, die zu viel sind
	 * 
	 * Der Wert kann nur einmal abgeholt werden, dabei wird er in dieser Klasse gelöscht.
	 */
	public synchronized int getshowOverloadwithPeople(){
		int tmp=showOverloadwithpeople;
		showOverloadwithpeople=0;
		return tmp;	
	}
	
	/**
	 * @brief Überlast-Anzeige-Flag Abfrage
	 *
	 * @return boolean flag (true=anzeigen, false=nicht anzeigen)
	 */
	public synchronized boolean getDisplayOverload(){
		return displayoverload;
	}
	
	/**
	 * @brief Überlast-Anzeige-Flag setzen/zurücksetzen
	 *
	 * @param flag - wenn auf dem Display "Überlast" angezeigt werden soll, so wird das flag auf true gesetzt, ansonsten false
	 * 
	 * Nur Werteschnittstelle, der Wert wird von der Eventqueue abgeholt
	 */
	public synchronized void setDisplayOverload(boolean flag){
		displayoverload=flag;
	}	
	
	/**
	 * @brief startet die Überlast-Routine 
	 * 
	 * @param Carry - Anzahl der Personen, die aussteigen
	 *
	 * erzeugt ein OverloadAction-Objekt
	 */
	public synchronized void startoverloadroutine(int Carry){
		setInterruptTime(-1);
		sound.Machine.playSound("overload.wav");
		overloadAction=new OverloadAction(Carry);
		overloadAction.start();
	}
	
	/**
	 * @brief stellt fest, ob in der aktuellen Etage angehalten werden muss
	 *
	 * @return booean - true, ja anhalten - false, nein nicht anhalten
	 */
	public boolean mussichanhalten(){
		if(driveup){
			if (hoch[Etage-1] || peoplerequest[Etage-1]!=0){
				return true;
			}
			if(runter[Etage-1]){
				//wenn keine fahrtwünsche darüber sind --> true else false
				if (isARequestoverme()){
					return false;
				}
				return true;
			}
			return false;
		}else{
			if (runter[Etage-1] || peoplerequest[Etage-1]!=0){
				return true;
			}
			if (hoch[Etage-1]){
				//wenn keine fahrtwünsche darunter sind --> true else false
				if(isARequestunderme())
					return false;
				return true;
			}
			return false;
		}
	}
	
	/** 
	* @class CabineDriveAction 
	* 
	* @brief die Klasse der Ablaufsteuerung
	* 
	* Die Ablaufsteuerung läuft in einem seperaten Thread und kümmert sich um die Personen, Fahrt, Türen, Wartezeiten, Stopps und Fahrtrichtungen.<br/>
	* Wenn es nichts zu tun gibt, wird der Thread beendet und wieder neu eröffnet, sobald es wieder eine Aufgabe gibt. Ein Thread bleibt solange bestehen, bis es wieder keine Aufgabe gibt.
	*/ 
	class CabineDriveAction extends Thread {
		private boolean dontplay;
		public void run(){
			while(true){
				dontplay=true; //wenn der Lift losfährt muss nichts abgespielt werden
				while(getdoorcond()!=0){
					closeDoors();
					if(doorcloseAction!=null){
						try {
							doorcloseAction.join();
						} catch (InterruptedException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
						if(doorcloseAction.wasinterrupted()){ //feststellen, ob das Türen schließen abgebrochen wurde.
							System.out.println("Türen-Schließen wurde abgebrochen.");
							doorcloseAction=null;
							interruptAction=new InterruptAction();
							// warten, bis die Türen auf sind
							if(dooropenAction!=null){
								try {
									dooropenAction.join();
								} catch (InterruptedException e) {
									// TODO Auto-generated catch block
									e.printStackTrace();
								}
							}
							//den Countdown erneut starten
							interruptAction.start();
							try {
								interruptAction.join();
							} catch (InterruptedException e) {
								// TODO Auto-generated catch block
								e.printStackTrace();
							}
						}else{
							doorcloseAction=null;
							System.out.println("Türen-Schließen wurde nicht abgebrochen.");
						}
							
					}
					
				}
				bewegung=true;
				
				if(isARequestoverme() && !isARequestunderme()){
					driveup=true;
				}else if(!isARequestoverme() && isARequestunderme())
					driveup=false;
				
				while(!isInterrupted() && 
					(
					((driveup==true) && ((getBewegung()!=0) || (!mussichanhalten())))  || 
					((driveup==false) && ((getBewegung()!=0) || (!mussichanhalten())))
					)
					){ //Bewegung muss ausgewertet werden, sonst kann man eine vorbeigefahrene Kabine anhalten
					
					if(driveup){
						pos++;
					
						if (pos==40){
							pos=0; 
							Etage++;
							if(!mussichanhalten())
							sound.Machine.playSound("fahrt.wav");
							
						}
					}else{
						if (pos==0){
							if(!dontplay){
								sound.Machine.playSound("fahrt.wav");
							}
								dontplay=false;
							
								
							pos=40;
							Etage--;
						}
						pos--;
					}
					try {
						Thread.sleep(125);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					} 
				}
				sound.Machine.playSound("arrive.wav");
				try {
					Thread.sleep(3500); //Zwischen Anhalten und Türöffnung
				} catch (InterruptedException e1) {
					// TODO Auto-generated catch block
					e1.printStackTrace();
				}
				bewegung=false;
				openDoors();
				
				if(driveup){
					hoch[Etage-1]=false;
					runter[Etage-1]=false;
				}else{
					runter[Etage-1]=false;
					hoch[Etage-1]=false;
				}
				try {
					Thread.sleep(2000); //Damit der Lift nach Ankunft nich sofort leer ist, wenn alle aussteigen
				} catch (InterruptedException e2) {
					// TODO Auto-generated catch block
					e2.printStackTrace();
				}	
				
				System.out.println("Es steigen "+peoplerequest[Etage-1]+" Personen aus.");
				peoplerequest[Etage-1]=0;
				try {
					if(dooropenAction!=null)
						dooropenAction.join();	//falls die Türen noch nicht vollständig geöffnet sind, wird hier gewartet.
				} catch (InterruptedException e1) {
					// TODO Auto-generated catch block
					e1.printStackTrace();
				}
				//darauf warten, dass die Zeit Abgelaufen ist.
				if(isARequestoverme() || isARequestunderme()){
					interruptAction=new InterruptAction();
					interruptAction.start();
					try {
						interruptAction.join();
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					setInterruptTime(-1); //damit das label weg geht 
					interruptAction=null;
				}
				/*try {
					Thread.sleep(4000);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}*/
				
				if(isARequestoverme()){
					if(isARequestunderme()){
						
					}else{
						driveup=true;
					}
				}else{
					if(isARequestunderme()){
						driveup=false;
					}else{
						break;
					}
				}
				System.out.print("Next Loop");
			}
			cabinedriveAction=null;
		}
	}
	
	
	/** 
	* @class InterruptAction 
	* 
	* @brief Der Ablauf für den Countdown
	* 
	* Diese Klasse ist ein seperater Thread, der den Countdown managed.
	* Er kann intrrupted werden.<br/>
	* wenn die InterruptAction abgelaufen ist, wird bei Bedarf eine neue CabineDriveAction erzeugt.
	*/ 
	class InterruptAction extends Thread{
		public void run(){
			boolean restart=true;
			int interntime=5000;
			setInterruptTime(4);
			System.out.println("Interrupt");
			while((!isInterrupted()) && (interntime>0)){
				if(gettimeinterruptflag()){
					restart=true;
				}else{
					if (restart){
						restart=false;
						interntime=5000;
						setInterruptTime(4);
					}
					interntime-=100;
					if(interntime%1000==0){
						setInterruptTime((interntime-1000)/1000);
					}
				}
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
					break;
				} 
			}
			if(cabinedriveAction==null){
				cabinedriveAction=new CabineDriveAction();
				cabinedriveAction.start();
			}
			interruptAction=null;
		}
	}
	
	/** 
	* @class OverloadAction 
	* 
	* @brief die Klasse für die Überlastbehandlung, sie läuft als seperater Thread
	* 
	* hier wird das Blinken wärend des Überlastzustandes gemanaged 
	*/ 
	class OverloadAction extends Thread{
		private int people; 
		public OverloadAction(int i) {
			super();
			people=i;
			
		}
		
		public void run(){
			for(int i=0; i<3 && (!isInterrupted()); i++){
				setDisplayOverload(true);
				try {
					Thread.sleep(750);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				setDisplayOverload(false);
				try {
					Thread.sleep(500);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			
			 setshowOverloadwith(people);
			 Carry=0;
		}
	}
	
	
	/** 
	* @class DoorCloseAction 
	* 
	* @brief die Klasse zum Schließen der Türen (seperater Thread)
	* 
	* in einem seperaten Thread werden die Türen geschlossen, dieser Thread ist interruptable!<br/>
	* dann bleiben die Türen an dieser Position stehen.
	*/ 
	class DoorCloseAction extends Thread{
		
		private boolean interrupted;
		
		DoorCloseAction(){
			interrupted=false;
		}
		
		public boolean wasinterrupted(){
			return interrupted;
		}
		
		public void run(){
			setInterruptTime(-1);
			while((!wasinterrupted()) && (door>0)){
				door--;
				try {
					Thread.sleep(50);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					interrupted=true;
					e.printStackTrace();
					break;
				} 
				if (isInterrupted()){
					interrupted=true;
				}
			}
		}
	}

	/** 
	* @class DoorOpenAction 
	* 
	* @brief die Klasse zum Öffnen der Türen (seperater Thread)
	* 
	* in einem seperaten Thread werden die Türen geöffnet, dieser Thread ist interruptable!<br/>
	* dann bleiben die Türen an dieser Position stehen.
	*/ 
	class DoorOpenAction extends Thread{
		public void run(){
			while((!isInterrupted()) && (door<40)){
				door++;
				try {
					Thread.sleep(50);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} 
			}
			dooropenAction=null;
		}
	}
}
